package org.mayanjun.code.dalint.handler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.OrderComparator;
import org.springframework.util.CollectionUtils;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * AnnotationBasedAutodiscoveryHandler
 *
 * @author mayanjun(9/20/16)
 */
public abstract class AnnotationBasedAutodiscoveryProcessor<T extends AccessHandler> extends SpringAutodiscoveryProcessor<T> {

	private static final Logger LOG = LoggerFactory.getLogger(AnnotationBasedAutodiscoveryProcessor.class);

	private volatile boolean handlersDiscovered;

	@Override
	public abstract Class<? extends Annotation> [] getAutodiscoveryTypes();

	@Override
	public List discoverHandlers() {
		synchronized (this) {
			List<T> handlers = new ArrayList<T>();
			if(handlersDiscovered) return handlers;

			Class<? extends Annotation> [] annoTypes = getAutodiscoveryTypes();
			if(annoTypes != null && annoTypes.length > 0) {
				for(Class<? extends Annotation> annoType : annoTypes) {
					List<T> hs = findHandlers(annoType);
					if(!CollectionUtils.isEmpty(hs)) handlers.addAll(hs);
				}
			}
			if(!handlers.isEmpty()) {
				Collections.sort(handlers, new OrderComparator());
			}
			handlersDiscovered = true;

			return handlers;
		}
	}

	private List<T> findHandlers(Class<? extends Annotation> annoType) {
		String [] packages = getPackages();
		Set<String> ps = null;
		if(packages != null && packages.length > 0) {
			ps = new TreeSet<String>(Arrays.asList(packages));
		}

		List<T> handlers = new ArrayList<T>();
		Map<String, Object> beans = this.applicationContext.getBeansWithAnnotation(annoType);
		if(beans != null && !beans.isEmpty()) {
			Iterator<Map.Entry<String, Object>> iterator = beans.entrySet().iterator();
			while (iterator.hasNext()) {
				Map.Entry<String, Object> entry = iterator.next();
				Object bean = entry.getValue();
				if(ps != null) {
					String pkg = bean.getClass().getPackage().getName();
					if(!ps.contains(pkg)) {
						LOG.info("Handler is not in the specified packages so that it will be ignored: beanId={}, handler={}",entry.getKey(),  bean.getClass().getSimpleName());
						continue;
					}
				}
				T handler;
				try {
					handler = (T) bean;
					handlers.add(handler);
					LOG.info("Handler found: beanId={}, handler={}", entry.getKey(), bean.getClass().getSimpleName());
				} catch (ClassCastException e) {
					LOG.warn("Handler type mismatch and this handler({}) will be discarded: {}", bean.getClass().getSimpleName(), e.getMessage());
				}
			}
		}
		return handlers;
	}
}
